﻿
/*
===========================================================================

  Copyright (c) 2010-2015 Darkstar Dev Teams

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see http://www.gnu.org/licenses/

  This file is part of DarkStar-server source code.

===========================================================================
*/
#include <string.h>

#include "../entities/charentity.h"
#include "../entities/mobentity.h"
#include "../party.h"
#include "../treasure_pool.h"
#include "charutils.h"
#include "../alliance.h"
#include "zoneutils.h"
#include "itemutils.h"
#include "battlefieldutils.h"
#include "mobutils.h"
#include "../battlefield.h"
#include "../battlefield_handler.h"
#include "../packets/entity_update.h"
#include "../ai/ai_container.h"

namespace battlefieldutils {
    /***************************************************************
        Loads the given battlefield from the database and returns
        a new Battlefield object.
    ****************************************************************/
    CBattlefield* loadBattlefield(CBattlefieldHandler* hand, uint16 bcnmid, BATTLEFIELDTYPE type) {
        const char* fmtQuery = "SELECT name, bcnmId, fastestName, fastestTime, timeLimit, levelCap, lootDropId, rules, partySize, zoneId, fastestPartySize, isMission \
                            FROM bcnm_info \
                            WHERE bcnmId = %u";

        int32 ret = Sql_Query(SqlHandle, fmtQuery, bcnmid);

        if (ret == SQL_ERROR ||
            Sql_NumRows(SqlHandle) == 0 ||
            Sql_NextRow(SqlHandle) != SQL_SUCCESS)
        {
            ShowError("Cannot load battlefield BCNM:%i \n", bcnmid);
        }
        else
        {
            CBattlefield* PBattlefield = new CBattlefield(hand, Sql_GetUIntData(SqlHandle, 1), type);
            char* tmpName = nullptr;
            Sql_GetData(SqlHandle, 0, &tmpName, nullptr);
            PBattlefield->setBcnmName((int8*)tmpName);
            PBattlefield->setTimeLimit(std::chrono::seconds(Sql_GetUIntData(SqlHandle, 4)));
            PBattlefield->setLevelCap(Sql_GetUIntData(SqlHandle, 5));
            PBattlefield->setLootId(Sql_GetUIntData(SqlHandle, 6));
            PBattlefield->setMaxParticipants(Sql_GetUIntData(SqlHandle, 8));
            PBattlefield->setZoneId(Sql_GetUIntData(SqlHandle, 9));
            PBattlefield->m_RuleMask = (uint16)Sql_GetUIntData(SqlHandle, 7);
            PBattlefield->m_isMission = (uint8)Sql_GetUIntData(SqlHandle, 11);

            PBattlefield->setRecord((const char*)Sql_GetData(SqlHandle, 2),
                (uint8)Sql_GetUIntData(SqlHandle, 10),
                std::chrono::seconds((uint32)Sql_GetUIntData(SqlHandle, 3)));

            return PBattlefield;
        }
        return nullptr;
    }

    /***************************************************************
        Spawns monsters for the given BCNMID/Battlefield number by
        looking at bcnm_battlefield table for mob ids then spawning
        them and adding them to the monster list for the given
        battlefield.
    ****************************************************************/
    bool spawnMonstersForBcnm(CBattlefield* battlefield) {
        DSP_DEBUG_BREAK_IF(battlefield == nullptr);

        //get ids from DB
        const char* fmtQuery = "SELECT monsterId, conditions \
                            FROM bcnm_battlefield \
                            WHERE bcnmId = %u AND battlefieldNumber = %u";

        int32 ret = Sql_Query(SqlHandle, fmtQuery, battlefield->getID(), battlefield->getBattlefieldNumber());

        if (ret == SQL_ERROR ||
            Sql_NumRows(SqlHandle) == 0)
        {
            ShowError("spawnMonstersForBcnm : SQL error - Cannot find any monster IDs for BCNMID %i Battlefield %i \n",
                battlefield->getID(), battlefield->getBattlefieldNumber());
        }
        else {
            while (Sql_NextRow(SqlHandle) == SQL_SUCCESS) {
                uint32 mobid = Sql_GetUIntData(SqlHandle, 0);
                uint8 condition = Sql_GetUIntData(SqlHandle, 1);
                CMobEntity* PMob = (CMobEntity*)zoneutils::GetEntity(mobid, TYPE_MOB);
                if (PMob != nullptr)
                {

                    PMob->m_battlefieldID = battlefield->getBattlefieldNumber();
                    PMob->m_bcnmID = battlefield->getID();

                    if (condition & CONDITION_SPAWNED_AT_START)
                    {
                        if (!PMob->PAI->IsSpawned())
                        {
                            if (strcmp((const char*)PMob->GetName(), "Maat") == 0) {
                                mobutils::InitializeMaat(PMob, (JOBTYPE)battlefield->getPlayerMainJob());

                                // disallow subjob, this will enable for later
                                battlefield->m_RuleMask &= ~RULES_ALLOW_SUBJOBS;

                            }
                            PMob->Spawn();

                            //ShowDebug("Spawned %s (%u) id %i inst %i \n",PMob->GetName(),PMob->id,battlefield->getID(),battlefield->getBattlefieldNumber());
                            battlefield->addEnemy(PMob, condition);
                        }
                        else {
                            ShowDebug(CL_CYAN"SpawnMobForBcnm: <%s> (%u) is already spawned\n" CL_RESET, PMob->GetName(), PMob->id);
                        }
                    }
                    else {
                        battlefield->addEnemy(PMob, condition);
                    }
                }
                else {
                    ShowDebug("SpawnMobForBcnm: mob %u not found\n", mobid);
                }
            }
            if (!(battlefield->m_RuleMask & RULES_ALLOW_SUBJOBS))
            {
                // disable players subjob
                battlefield->disableSubJob();
            }
            return true;
        }
        return false;
    }



    /***************************************************************
        Spawns treasure chest/armory crate, what ever on winning bcnm
    ****************************************************************/
    bool spawnTreasureForBcnm(CBattlefield* battlefield) {
        DSP_DEBUG_BREAK_IF(battlefield == nullptr);

        //get ids from DB
        const char* fmtQuery = "SELECT npcId \
                            FROM bcnm_treasure_chests \
                            WHERE bcnmId = %u AND battlefieldNumber = %u";

        int32 ret = Sql_Query(SqlHandle, fmtQuery, battlefield->getID(), battlefield->getBattlefieldNumber());

        if (ret == SQL_ERROR || Sql_NumRows(SqlHandle) == 0)
        {
            ShowError("spawnTreasureForBcnm : SQL error - Cannot find any npc IDs for BCNMID %i Battlefield %i \n",
                battlefield->getID(), battlefield->getBattlefieldNumber());
        }
        else
        {
            while (Sql_NextRow(SqlHandle) == SQL_SUCCESS)
            {
                uint32 npcid = Sql_GetUIntData(SqlHandle, 0);
                CBaseEntity* PNpc = (CBaseEntity*)zoneutils::GetEntity(npcid, TYPE_NPC);
                if (PNpc != nullptr)
                {
                    PNpc->status = STATUS_NORMAL;
                    PNpc->animation = 0;
                    PNpc->loc.zone->PushPacket(PNpc, CHAR_INRANGE, new CEntityUpdatePacket(PNpc, ENTITY_SPAWN, UPDATE_ALL_MOB));
                    battlefield->addNpc(PNpc);
                    ShowDebug(CL_CYAN"Spawned %s id %i inst %i \n", PNpc->status, PNpc->id, battlefield->getBattlefieldNumber());
                }
                else
                {
                    ShowDebug(CL_CYAN"spawnTreasureForBcnm: <%s> is already spawned\n" CL_RESET, PNpc->GetName());
                }
            }
            return true;
        }
        return false;
    }



    /**************************************************************
    Called by ALL BCNMs to check winning conditions every tick. This
    is usually when all the monsters are defeated but can be other things
    (e.g. mob below X% HP, successful Steal, etc)
    ***************************************************************/
    bool meetsWinningConditions(CBattlefield* battlefield, time_point tick)
    {
        return battlefield->won() || battlefield->allEnemiesDefeated();
    }

    /**************************************************************
    Called by ALL BCNMs to check losing conditions every tick. This
    will be when everyone is dead and the death timer is >3min (usually)
    or when everyone has left, etc.
    ****************************************************************/
    bool meetsLosingConditions(CBattlefield* battlefield, time_point tick) {
        if (battlefield->lost()) return true;
        //check for expired duration e.g. >30min. Need the tick>start check as the start can be assigned
        //after the tick initially due to threading
        if (tick > battlefield->getStartTime() && (tick - battlefield->getStartTime()) > battlefield->getTimeLimit()) {
            ShowDebug("BCNM %i inst:%i - You have exceeded your time limit!\n", battlefield->getID(),
                battlefield->getBattlefieldNumber());
            return true;
        }

        battlefield->lastTick = tick;

        //check for all dead for 3min (or whatever the rule mask says)
        if (battlefield->getDeadTime() != time_point::min()) {
            if (battlefield->m_RuleMask & RULES_REMOVE_3MIN) {
                //  if(((tick - battlefield->getDeadTime())/1000) % 20 == 0){
                //      battlefield->pushMessageToAllInBcnm(200,180 - (tick - battlefield->getDeadTime())/1000);
                //  }
                if (tick - battlefield->getDeadTime() > 3min) {
                    ShowDebug("All players from the battlefield %i inst:%i have fallen for 3mins. Removing.\n",
                        battlefield->getID(), battlefield->getBattlefieldNumber());
                    return true;
                }
            }
            else {
                ShowDebug("All players have fallen. Failed battlefield %i inst %i. No 3min mask. \n", battlefield->getID(), battlefield->getBattlefieldNumber());
                return true;
            }
        }

        return false;
    }

    /*************************************************************
    Returns the losing exit position for this BCNM.
    ****************************************************************/
    void getLosePosition(CBattlefield* battlefield, int(&pPosition)[4]) {
        if (battlefield == nullptr)
            return;

        switch (battlefield->getZoneId()) {
            case 139: //Horlais Peak
                pPosition[0] = -503; pPosition[1] = 158; pPosition[2] = -212; pPosition[3] = 131;
                break;
        }
    }

    void getStartPosition(uint16 zoneid, float(&pPosition)[4]) {

        switch (zoneid) {
            case 139: //Horlais Peak
                pPosition[0] = -503; pPosition[1] = 158; pPosition[2] = -212; pPosition[3] = 131;
                break;
            case 144: //Waug. Shrine
                pPosition[0] = -361; pPosition[1] = 100; pPosition[2] = -260; pPosition[3] = 131;
                break;
            case 146: //Balgas Dias
                pPosition[0] = 317; pPosition[1] = -126; pPosition[2] = 380; pPosition[3] = 131;
                break;
            case 165: //Throne Room
                pPosition[0] = 114; pPosition[1] = -8; pPosition[2] = 0; pPosition[3] = 131;
                break;
            case 206: //QuBia Arena
                pPosition[0] = -241; pPosition[1] = -26; pPosition[2] = 20; pPosition[3] = 131;
                break;
        }
    }

    /*************************************************************
    Returns the winning exit position for this BCNM.
    ****************************************************************/
    void getWinPosition(CBattlefield* battlefield, int(&pPosition)[4]) {
        if (battlefield == nullptr)
            return;

        switch (battlefield->getZoneId()) {
            case 139: //Horlais Peak
                pPosition[0] = 445; pPosition[1] = -38; pPosition[2] = -19; pPosition[3] = 200;
                break;
        }
    }

    /*************************************************************
    Get loot from the armoury crate
    ****************************************************************/

    void getChestItems(CBattlefield* battlefield) {
        int instzone = battlefield->getZoneId();
        uint8 maxloot = 0;
        LootList_t* LootList = itemutils::GetLootList(battlefield->getLootId());

        if (LootList == nullptr) {
            ShowError("BCNM Chest opened with no valid loot list!");
            //no loot available for bcnm. End bcnm.
            battlefield->winBcnm();
            return;
        }
        else
        {
            for (uint8 sizeoflist = 0; sizeoflist < LootList->size(); ++sizeoflist) {
                if (LootList->at(sizeoflist).LootGroupId > maxloot) {
                    maxloot = LootList->at(sizeoflist).LootGroupId;
                }
            }
        }

        if (maxloot != 0)
        {
            for (uint8 group = 0; group <= maxloot; ++group)
            {
                uint16 groupRoll = dsprand::GetRandomNumber(1000/* aka 100.0% */);
                uint16 itemRolls = 0;

                for (uint8 item = 0; item < LootList->size(); ++item)
                {
                    if (group == LootList->at(item).LootGroupId)
                    {
                        itemRolls += LootList->at(item).Rolls;
                        if (groupRoll <= itemRolls)
                        {
                            battlefield->m_PlayerList.at(0)->PTreasurePool->AddItem(LootList->at(item).ItemID, battlefield->m_NpcList.at(0));
                            break;
                        }
                    }
                }
            }
        }
        //user opened chest, complete bcnm
        if (instzone != 37 && instzone != 38) {
            battlefield->winBcnm();
        }
        else {
            battlefield->m_NpcList.clear();
        }

    }

    bool spawnSecondPartDynamis(CBattlefield* battlefield) {
        DSP_DEBUG_BREAK_IF(battlefield == nullptr);

        //get ids from DB
        const char* fmtQuery = "SELECT monsterId \
                                FROM bcnm_battlefield \
                                WHERE bcnmId = %u AND battlefieldNumber = 2";

        int32 ret = Sql_Query(SqlHandle, fmtQuery, battlefield->getID());

        if (ret == SQL_ERROR ||
            Sql_NumRows(SqlHandle) == 0)
        {
            ShowError("spawnSecondPartDynamis : SQL error - Cannot find any monster IDs for Dynamis %i \n",
                battlefield->getID(), battlefield->getBattlefieldNumber());
        }
        else {
            while (Sql_NextRow(SqlHandle) == SQL_SUCCESS) {
                uint32 mobid = Sql_GetUIntData(SqlHandle, 0);
                CMobEntity* PMob = (CMobEntity*)zoneutils::GetEntity(mobid, TYPE_MOB);
                if (PMob != nullptr)
                {
                    if (!PMob->PAI->IsSpawned())
                    {
                        PMob->Spawn();

                        PMob->m_battlefieldID = battlefield->getBattlefieldNumber();

                        ShowDebug("Spawned %s (%u) id %i inst %i \n", PMob->GetName(), PMob->id, battlefield->getID(), battlefield->getBattlefieldNumber());
                        battlefield->addEnemy(PMob, CONDITION_SPAWNED_AT_START & CONDITION_WIN_REQUIREMENT);
                    }
                    else {
                        ShowDebug(CL_CYAN"spawnSecondPartDynamis: <%s> (%u) is already spawned\n" CL_RESET, PMob->GetName(), PMob->id);
                    }
                }
                else {
                    ShowDebug("spawnSecondPartDynamis: mob %u not found\n", mobid);
                }
            }
            return true;
        }
        return false;
    }
};
